Popovers are everywhere on the web. You can see them in menus, toggletips, and dialogs, which could manifest as account settings, disclosure widgets, and product card previews. Despite how prevalent these components are, building them in browsers is still surprisingly cumbersome. You need to add scripting to manage focus, open and close states, accessible hooks into the components, keyboard bindings to enter and exit the experience, and that’s all even before you start building the useful, unique, core functionality of your popover.
To resolve this, a new set of declarative HTML APIs for building popovers is coming to browsers, starting with the popover
API in Chromium 114.
The popover attribute
Rather than managing all of the complexity yourself, you can let the browser handle it with the popover
attribute and subsequent set of features. HTML popovers support:
- Promotion to the top layer. Popovers will appear on a separate layer above the rest of the page, so you don’t have to futz around with z-index.
- Light-dismiss functionality. Clicking outside of the popover area will close the popover and return focus.
- Default focus management. Opening the popover makes the next tab stop inside the popover.
- Accessible keyboard bindings. Hitting the
esc
key will close the popover and return focus. - Accessible component bindings. Connecting a popover element to a popover trigger semantically.
You can now build popovers with all of these features without using JavaScript. A basic popover requires three things:
- A
popover
attribute on the element containing the popover. - An
id
on the element containing the popover. - A
popovertarget
with the value of the popover'sid
on the element that opens the popover.
<button popovertarget="my-popover"> Open Popover </button>
<div id="my-popover" popover>
<p>I am a popover with more information.</p>
</div>
Now you have a fully-functional basic popover.
This popover could be used to convey additional information or as a disclosure widget.
Defaults and overrides
By default, such as in the previous code snippet, setting up a popover with a popovertarget
means the button or element that opens the popover will toggle it open and closed. However, you can also create explicit popovers using popovertargetaction
. This overrides the default toggle action. popovertargetaction
options include:
popovertargetaction="show"
: Shows the popover.
popovertargetaction="hide"
: Hides the popover.
Using popovertargetaction="hide"
, you can create a “close” button within a popover, as in the following snippet:
<button popovertarget="my-popover" popovertargetaction="hide">
<span aria-hidden="true">❌</span>
<span class="sr-only">Close</span>
</button>
Auto versus manual popovers
Using the popover
attribute on its own is actually a shortcut for popover="auto"
. When opened, the default popover
will force close other auto popovers, except for ancestor popovers. It can be dismissed via light-dismiss or a close button.
On the other hand, setting popover=manual
creates another type of popover: a manual popover. These do not force close any other element type and do not close via light-dismiss. You must close them via a timer or explicit close action. Types of popovers appropriate for popover=manual
are elements which appear and disappear, but shouldn't affect the rest of the page, such as a toast notification.
If you explore the demo above, you can see that clicking outside of the popover area doesn't light-dismiss the popover. Additionally, if there were other popovers open, they wouldn't close.
To review the differences:
Popovers with popover=auto
:
- When opened, force-close other popovers.
- Can light-dismiss.
Popovers with popover=manual
:
- Do not force close any other element type.
- Do not light-dismiss. Close them using a toggle or close action.
Styling popovers
So far you've learned about basic popovers in HTML. But there are also some nice styling features that come with popover
. One of those is the ability to style ::backdrop
.
In auto
popovers, this is a layer directly beneath the top layer (where the popover lives), which sits above the rest of the page. In the following example, the ::backdrop
is given a semi-transparent color:
#size-guide::backdrop {
background: rgb(190 190 190 / 50%);
}
The difference between a popover
and a dialog
It's important to note that the popover
attribute does not provide semantics on its own. And while you can now build modal dialog-like experiences using popover="auto"
, there are a few key differences between the two:
A dialog
element opened with dialog.showModal
(a modal dialog), is an experience which requires explicit user interaction to close the modal.
A popover
supports light-dismiss. A modal dialog
does not.
A modal dialog makes the rest of the page inert. A popover
does not.
The above demo is a semantic dialog with popover behavior. This means that the rest of the page is not inert and that the dialog popover does get light-dismiss behavior. You can build this dialog with popover behavior using the following code:
<button popovertarget="candle-01">
Quick Shop
</button>
<dialog popover id="candle-01">
<button class="close-btn" popovertarget="candle-01" popovertargetaction="hide">...</button>
<div class="product-preview-container">
...
</div>
</dialog>
Coming soon
Interactive entry and exit
The ability to animate discrete properties, including animating to and from display: none
and animating to and from the top layer are not yet available in browsers. However, they are planned for an upcoming version of Chromium, closely following this release.
With the ability to animate discrete properties, and using :popover-open
and @starting-style
, you'll be able to set up before-change and after-change styles to enable smooth transitions when opening and closing popovers. Take the previous example. Animating it in and out looks much smoother and supports a more fluid user experience:
Anchor positioning
Popovers are great when you want to position an alert, modal, or notification based on the viewport. But popovers are also useful for menus, tooltips, and other elements that need to be positioned relative to other elements. This is where CSS anchoring comes in.
The following radial menu demo uses the popover API along with CSS anchor positioning to ensure that the popover #menu-items
is always anchored to its toggle trigger, the #menu-toggle
button.
Setting up anchors is similar to setting up popovers:
<button id="menu-toggle" popovertarget="menu-items">
Open Menu
</button>
<ul id="menu-items" popover anchor="menu-toggle">
<li class="item">...</li>
<li class="item">...</li>
</ul>
You set up an anchor by giving it an id
(in this example, #menu-toggle
), and then use anchor="menu-toggle"
to connect the two elements. Now, you can use anchor()
to style the popover. A centered popover menu that is anchored to the baseline of the anchor toggle might be styled as follows:
#menu-items {
bottom: anchor(bottom);
left: anchor(center);
translate: -50% 0;
}
Now you have a fully-functional popover menu that is anchored to the toggle button and has all of the built-in features of popover, no JavaScript required!
Conclusion
The popover API is the first step in a series of new capabilities to make building web applications easier to manage and more accessible by default. I'm excited to see how you use popovers!